﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using vJine.Core;

namespace vJine.Lua.GUI.BLL {
    public class FormulaEvaluator {
        public int times { get; set; }
        public int counter { get; set; }
        public int step { get; set; }

        public bool disable { get; set; }
        public bool pause { get; set; }
        public string pause_message { get; set; }

        public double delay { get; set; }

        string param_INDEX = null;
        string cmd_formula = null;

        FormulaEvaluator formulaBefore = null;
        List<FormulaEvaluator> formulaParams = null;
        FormulaEvaluator formulaAfter = null;

        public FormulaEvaluator() {
        }

        public FormulaEvaluator(LuaContext context, NodeConfig nConfig, string formula) {
            this.compile(context, nConfig, formula);
        }

        int?[] PARAM_FIELD_INDEX = null;
        List<string[]> PARAM_DATA = null;
        NodeConfig RunConfig = null;
        public int Compile(LuaContext context, NodeConfig runConfig, string param_file) {
            if (!string.IsNullOrEmpty(param_file)) {
                if (!File.Exists(param_file)) {
                    throw new CoreException("参数文件不存在：{0}", param_file);
                } else {

                }
            }

            this.RunConfig = runConfig;

            NodeConfig Params = runConfig.configs;
            if (Params == null || Params.Count == 0) {
                return 0;
            }

            //检查重复[参数名称]
            for (int i = 0, len_i = Params.Count; i < len_i; i++) {
                string param_i_name = Params[i].param;
                if (string.IsNullOrEmpty(param_i_name)) {
                    continue;
                }

                for (int j = i + 1; j < len_i; j++) {
                    string param_j_name = Params[j].param;
                    if (param_i_name == param_j_name) {
                        throw new CoreException("参数名称[{0}]重复,[{1}]-[{2}]", param_i_name, Params[i].alias, Params[j].alias);
                    }
                }
            }

            //解析参数文件
            if (!string.IsNullOrEmpty(param_file)) {
                string[] param_data_temp = File.ReadAllLines(param_file,Encoding.Default);
                if (param_data_temp == null || param_data_temp.Length < 2) {
                    throw new CoreException("参数文件中无数据：{0}", param_file);
                }
                if (string.IsNullOrEmpty(param_data_temp[0])) {
                    throw new CoreException("参数文件首行无字段：{0}", param_file);
                }
                //解析字段索引
                string[] param_fields = param_data_temp[0].Split(',');
                this.PARAM_FIELD_INDEX = new int?[Params.Count];
                for (int i = 0, len = param_fields.Length; i < len; i++) {
                    string field_i = param_fields[i];
                    if (string.IsNullOrEmpty(field_i)) {
                        throw new CoreException("参数文件中包含空字段名：{0}", param_file);
                    } else if (field_i.EndsWith("-")) {
                        continue;
                    }

                    for (int j = 0, len_j = Params.Count; j < len_j; j++) {
                        NodeConfig param_j = Params[j];
                        if (param_j.w && param_j.param == field_i) {
                            this.PARAM_FIELD_INDEX[j] = i; break;
                        }
                    }
                }
                //判断缺失字段
                string err_lost_fields = "";
                for (int i = 0, len = param_fields.Length; i < len; i++) {
                    string field_i = param_fields[i];
                    if (field_i.EndsWith("-")) {
                        continue;
                    }

                    bool is_found = false;
                    for (int j = 0, len_j = this.PARAM_FIELD_INDEX.Length; j < len_j; j++) {
                        if (i == this.PARAM_FIELD_INDEX[j]) {
                            is_found = true;
                        }
                    }
                    if (!is_found) {
                        err_lost_fields +=
                            (err_lost_fields == "" ? "" : ",") + field_i;
                    }
                }
                if (!string.IsNullOrEmpty(err_lost_fields)) {
                    throw new CoreException("参数文件中的字段名[{0}]在[输入参数]中未找到，参数文件:{1}", err_lost_fields, param_file);
                }
                //解析数据
                this.PARAM_DATA = new List<string[]>();
                for (int i = 1, len = param_data_temp.Length; i < len; i++) {
                    //忽略空行
                    if (string.IsNullOrEmpty(param_data_temp[i])) {
                        continue;
                    } else {
                        string[] param_temp_data_i = param_data_temp[i].Split(',');
                        if (param_temp_data_i.Length != param_fields.Length) {
                            throw new CoreException("参数文件数据格式错误：数据行,{0};文件,{1}", i + 1, param_file);
                        }
                        this.PARAM_DATA.Add(param_temp_data_i);
                    }
                }
            }

            if (!string.IsNullOrEmpty(runConfig.formula_before)) {
                this.formulaBefore =
                    new FormulaEvaluator(context, this.RunConfig, runConfig.formula_before);
            }

            this.formulaParams = this.compile_params(context, this.RunConfig);

            if (!string.IsNullOrEmpty(runConfig.formula_after)) {
                this.formulaAfter =
                    new FormulaEvaluator(context, this.RunConfig, runConfig.formula_after);
            }

            return this.PARAM_DATA == null ? 0 : this.PARAM_DATA.Count;
        }

        public void eval_before(LuaContext context, NodeConfig runConfig = null) {
            if (this.formulaBefore != null) {
                this.formulaBefore.eval(context, runConfig ?? this.RunConfig);
            }
        }

        public void eval_params(LuaContext context, NodeConfig parent, int param_index,NodeConfig runConfig = null) {
            if (this.formulaParams == null || this.formulaParams.Count == 0) {
                return;
            }

            for (int i = 0, len = this.Params.Count; i < len; i++) {
                NodeConfig param_i = Params[i];
                if (param_i.Parent != parent) {
                    continue;
                }

                param_i.trig_on_changed = false;
                object objR = null;
                if (!string.IsNullOrEmpty(this.formulaParams[i].cmd_formula)) {
                    objR = this.formulaParams[i].eval(context, runConfig ?? this.RunConfig);
                } else if (this.PARAM_FIELD_INDEX != null && this.PARAM_FIELD_INDEX[i] != null) {
                    objR = this.PARAM_DATA[param_index - 1][this.PARAM_FIELD_INDEX[i].Value];
                }

                if (objR != null) {
                    if (param_i.list != null) {
                        for (int j = 0, len_j = param_i.list.Count; j < len_j; j++) {
                            ListItem li = param_i.list[j];
                            li.Checked = object.Equals(objR, li.Value);
                        }
                    } else {
                        param_i.value = objR;
                    }
                    param_i.NotifyPropertyChanged("value");
                }
            }
        }

        public void eval_after(LuaContext context) {
            if (this.formulaAfter != null) {
                this.formulaAfter.eval(context, this.RunConfig);
            }
        }

        //获取参数默认值
        List<Object> get_defaults(NodeConfig runConfig) {
            List<Object> defaults = new List<object>();
            if (runConfig.configs == null) {
                return defaults;
            }

            for (int i = 0, len = runConfig.configs.Count; i < len; i++) {
                defaults.Add(runConfig.configs[i].value);
            }
            return defaults;
        }

        public void set_defaults() {
            this.set_defaults(this.RunConfig, this.Defaults);
        }

        void set_defaults(NodeConfig runConfig, List<object> defaults) {
            if (runConfig == null || runConfig.configs == null) {
                return;
            }
            if (defaults == null || defaults.Count == 0) {
                return;
            }

            for (int i = 0, len = runConfig.configs.Count; i < len; i++) {
                NodeConfig param_i = runConfig.configs[i];
                param_i.trig_on_changed = true;

                if (!param_i.R) {
                    continue;
                }
                param_i.value = defaults[i];
                
            }
        }

        void compile(LuaContext context, NodeConfig nConfig, string formula) {
            if (string.IsNullOrEmpty(formula)) {
                this.cmd_formula = null;
                return;
            }

            this.cmd_formula = "__cmd_formula_" + Crypto.MD5(formula).Substring(8, 16);
            this.param_INDEX = "__INDEX" + cmd_formula;
            if (context.is_function(this.cmd_formula)) {
                return;
            }

            string cmd_formula_helper = "__HELPER" + cmd_formula;

            context.eval(string.Format(@"function {0} (ctx, sub)
                        {1}
                    end",
                cmd_formula, formula));

            context.reg(cmd_formula_helper, new Call<object, string>((string key) => {
                return this.get_param(key);
            }));

            context.eval(string.Format(
                "{0} = {{__index = function(_, key) return {1}(key) end}}",
                this.param_INDEX, cmd_formula_helper
                ));
        }

        List<FormulaEvaluator> compile_params(LuaContext context, NodeConfig runConfig) {
            NodeConfig Params = runConfig.configs;
            if (Params == null || Params.Count == 0) {
                return null;
            }
            this.Params = runConfig.configs;
            this.Defaults = this.get_defaults(runConfig);

            List<FormulaEvaluator> formulas = new List<FormulaEvaluator>();
            for (int i = 0, len = Params.Count; i < len; i++) {
                NodeConfig param_i = Params[i];

                FormulaEvaluator formulaEva =
                    new FormulaEvaluator(
                    context, param_i, param_i.formula) {
                        Params = this.Params, Defaults = this.Defaults
                    };
                formulas.Add(formulaEva);
            }
            return formulas;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="runConfig"></param>
        /// <param name="fetch">如果为True，则将脚本值回设到Context</param>
        object eval(LuaContext context, NodeConfig runConfig) {
            if (string.IsNullOrEmpty(this.cmd_formula)) {
                return null;
            }

            this.times = runConfig.times;
            this.counter = runConfig.counter;

            this.disable = runConfig.disable;
            this.pause = runConfig.pause;
            this.pause_message = runConfig.pause_message;

            this.delay = runConfig.delay;

            {
                context.set<FormulaEvaluator>(this);
                context.get(this.param_INDEX);
                context.nexec("setmetatable", 2, false);
                context.copy();
            }
            try {
                object[] objR = context.nexec(this.cmd_formula, 1);

                if (objR != null && objR.Length > 0) {
                    return objR[0];
                } else {
                    return null;
                }
            } finally {
                context.get<FormulaEvaluator>(this);
                this.get_config(runConfig);
            }
        }

        void get_config(NodeConfig nConfig) {
            if (nConfig == null) {
                return;
            }

            nConfig.times = nConfig.times;
            nConfig.counter = this.counter;

            nConfig.disable = this.disable;
            nConfig.pause = this.pause;
            nConfig.pause_message = this.pause_message;

            nConfig.delay = this.delay;
        }

        object get_param(string key) {
            if (this.Params == null || this.Params.Count == 0) {
                return null;
            }

            bool is_default = key.EndsWith("_");
            if (is_default) {
                key = key.Substring(0, key.Length - 1);
            }
            for (int i = 0, len = this.Params.Count; i < len; i++) {
                NodeConfig param_i = this.Params[i];
                if (param_i.param == key) {
                    if (is_default) {
                        return this.Defaults[i];
                    } else {
                        return param_i.value;
                    }
                }
            }

            return null;
        }

        internal NodeConfig Params;
        internal List<object> Defaults;
    }
}
